#include "StdAfx.h"
#include "GeoClipmapTerrain.h"

GeoClipmapTerrain::GeoClipmapTerrain(void)
:	clipmap_n_(0),
	buffer_n_(0),
	heightScale_(1.0f),
	viewer_x_(0.0f),
	viewer_z_(0.0f),
	buffer_x_(0.0f),
	buffer_z_(0.0f),
	dib_(NULL),
	buffer_ext_(10),
	map_w_(0),
	map_h_(0)
{
}

GeoClipmapTerrain::~GeoClipmapTerrain(void)
{
}

static double logbase(double a, double base)
{
	return log(a) / log(base);
}

int GeoClipmapTerrain::Init(int clipmapN, float heightScale, const char *hmFilePath, int x, int z)
{
	assert(clipmapN > 1);
	assert(hmFilePath);
	if (clipmapN <= 1 || !hmFilePath)
	{
		return -1;
	}

	viewer_x_ = x;
	viewer_z_ = z;
	heightScale_ = heightScale;

	// height map info
	dib_ = FreeImage_Load(FIF_JPEG, hmFilePath);
	assert(dib_);
	if (!dib_)
	{
		return -2;
	}
	map_w_ = FreeImage_GetWidth(dib_);
	map_h_ = FreeImage_GetHeight(dib_);

	clipmap_n_ = clipmapN;	
	buffer_n_ = clipmap_n_ + buffer_ext_;

	// alloc mem clipmap buffers and info
	int clipNum = (int)logbase((min(map_w_, map_h_)/clipmap_n_), 2);
	int bufferSize = buffer_n_ * buffer_n_;
	for (int i = 0; i < clipNum; ++i)
	{
		ClipLevelInfo info;
		info.buffer = new char[bufferSize];
		memset(info.buffer, 0, bufferSize);
		info.interval = pow(2.0f, i);
		info.buffer_x_ = clipmap_n_/2;
		info.buffer_z_ = clipmap_n_/2;
		
		// fill clipmap mem buffer
		for (int z = 0; z < buffer_n_; ++z)
		{
			int b_x = 0;
			int b_z = z;
			int f_x = viewer_x_ - (buffer_n_/2)*info.interval;
			int f_z = map_h_- 1 - (viewer_z_+z)*info.interval;
			UpdateBufferBand(info.buffer, b_x, b_z, 1, 0,
				f_x, f_z, info.interval, 0,
				buffer_n_);
		}

		// alloc texture gmem
		GLuint texId;
		glGenTextures(1, &texId);
		info.texId = texId;

		// fill gmem buffer	
		glBindTexture(GL_TEXTURE_2D, texId);
		// glTexSubImage2D can't create new texture?
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, clipmap_n_, clipmap_n_, 0, GL_RED, GL_UNSIGNED_BYTE, info.buffer);
		for (int z = 0; z < clipmap_n_; ++z)
		{
			int b_z = z + buffer_ext_/2;
			int b_x = buffer_ext_/2;
			glTexSubImage2D(GL_TEXTURE_2D, 0, b_x, b_z, clipmap_n_, 1, GL_RED, GL_UNSIGNED_BYTE, info.buffer + b_z*buffer_n_ + b_x);
		}

		clipLevels_.push_back(info);
	}

	return 0;
}

void GeoClipmapTerrain::Fini()
{
	for (std::vector<ClipLevelInfo>::iterator iter = clipLevels_.begin(); iter != clipLevels_.end(); ++iter)
	{
		delete iter->buffer;
		glDeleteTextures(1, &iter->texId);
	}
	FreeImage_Unload(dib_);
}

void GeoClipmapTerrain::OnViewerMove( int xOffset, int zOffset )
{
	int xSign = xOffset/abs(xOffset);
	int zSign = zOffset/abs(zOffset);

	for (int i = 0; i < abs(xOffset); ++i) // x dir update
	{
		for (std::vector<ClipLevelInfo>::iterator iter = clipLevels_.begin(); iter != clipLevels_.end(); ++iter)
		{
			if (0 == viewer_x_%iter->interval)
			{
				int b_x = (iter->buffer_x_-xSign*(int)(buffer_n_/2))%buffer_n_;
				int b_z = (iter->buffer_z_-(int)(buffer_n_/2))%buffer_n_;
				// border situation?
				int file_x = viewer_x_ + xSign*(int)(buffer_n_/2+1)*iter->interval;
				int file_z = map_h_ - 1 - (viewer_z_-(int)(buffer_n_/2)*iter->interval);
				UpdateBufferBand(iter->buffer, b_x, b_z, 0, 1,
					file_x, file_z, 0, -1, 
					buffer_n_);
				iter->buffer_x_ = (iter->buffer_x_+xSign)%buffer_n_;
			}
		}
		viewer_x_ += xSign;
	}

	for (int i = 0; i < abs(zOffset); ++i) // z dir update
	{
		for (std::vector<ClipLevelInfo>::iterator iter = clipLevels_.begin(); iter != clipLevels_.end(); ++iter)
		{
			if (0 == viewer_x_%iter->interval)
			{
				int b_x = (buffer_x_ - (int)(buffer_n_/2))%buffer_n_;
				int b_z = (buffer_z_ - zSign*(int)(buffer_n_/2))%buffer_n_;
				// border situation?
				int file_x = viewer_x_ - (int)(buffer_n_/2)*iter->interval;
				int file_z = map_h_ - 1 - (viewer_z_+zSign*(int)(buffer_n_/2+1)*iter->interval);
				UpdateBufferBand(iter->buffer, b_x, b_z, 1, 0, 
					file_x, file_z, 1, 0,
					buffer_n_);
				iter->buffer_z_ = (iter->buffer_z_+zSign)%buffer_n_;
			}
		}
		viewer_z_ += zSign;
	}
}

void GeoClipmapTerrain::UpdateBufferBand(char* buffer, int bufferInitX, int bufferInitZ, int bufferXStride, int bufferZStride,
										 int fileInitX, int fileInitY, int fileXStride, int fileYStride, 
										 int count)
{
	for (int i = 0; i < count; ++i)
	{
		BYTE b;
		FreeImage_GetPixelIndex(dib_, fileInitX + i*fileXStride, fileInitY + i*fileYStride, &b);
		int b_pos = (bufferInitZ+i*bufferZStride)%buffer_n_*buffer_n_ + (bufferInitX+i*bufferXStride)%buffer_n_;
		buffer[b_pos] = b;
	}
}
